/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) RegionDataReader.java 2018-08-19 17:28
 */

package cn.jh.common.core.cn;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.jetbrains.annotations.Nullable;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import static cn.jh.common.core.cn.RegionConstants.CITY_COUNT;
import static cn.jh.common.core.cn.RegionConstants.COUNTY_COUNT;
import static cn.jh.common.core.cn.RegionConstants.REGIONS_RES_PATH;
import static cn.jh.common.core.cn.RegionConstants.STATE_COUNT;
import static java.nio.file.StandardOpenOption.READ;


/**
 * @author Fuchun
 * @since 1.0
 */
class RegionDataReader {

    private final ReentrantLock lock = new ReentrantLock();

    final Map<String, Region> stateMap;
    final Map<String, Region> cityMap;
    final Map<String, Region> countyMap;
    final Multimap<String, String> state2CityCodeMap;
    final Multimap<String, String> city2CountyCodeMap;

    RegionDataReader() {
        stateMap = new HashMap<>(STATE_COUNT);
        cityMap = new HashMap<>(CITY_COUNT);
        countyMap = new HashMap<>(COUNTY_COUNT);
        state2CityCodeMap = HashMultimap.create(STATE_COUNT, 24);
        city2CountyCodeMap = HashMultimap.create(CITY_COUNT, 24);
    }

    void initRegions() {
        if (!(stateMap.isEmpty() && cityMap.isEmpty() && countyMap.isEmpty())) {
            return;
        }
        lock.lock();
        try {
            if (stateMap.isEmpty() && cityMap.isEmpty() && countyMap.isEmpty()) {
                loadRegionData();
            }
        } finally {
            lock.unlock();
        }
    }

    private void loadRegionData() {
        InputStream inputStream = getClass().getResourceAsStream(REGIONS_RES_PATH);
        if (inputStream == null) {
            throw new IllegalStateException(REGIONS_RES_PATH + " file not found.");
        }
        try (DataInputStream input = new DataInputStream(inputStream)) {
            readData(input, stateMap);
            readData(input, cityMap);
            readData(input, countyMap);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    private void readRegion(
            DataInputStream input, RegionLevel level, Map<String, Region> regionMap, @Nullable String parent)
            throws IOException {
        String code = input.readUTF();
        String name = input.readUTF();
        String fullName = input.readUTF();
        input.readByte(); // ignore comma -> ','
        Region r = new Region.Builder()
                .setCode(code).setName(name).setLevel(level)
                .setFullName(fullName).setParent(parent)
                .build();
        regionMap.put(r.getCode(), r);
        if (level == RegionLevel.CITY) {
            state2CityCodeMap.put(parent, code);
        } else if (level == RegionLevel.COUNTY) {
            city2CountyCodeMap.put(parent, code);
        }
    }

    private void readData(DataInputStream input, Map<String, Region> regionMap) throws IOException {
        String levelVal = input.readUTF();
        RegionLevel level = RegionLevel.forString(levelVal);
        boolean isState = level == RegionLevel.STATE;
        int size = input.readShort();
        int nextSize;
        String parent;
        input.readByte(); // ignore colon -> ':'
        int i, j;
        for (i = 0; i < size; i++) {
            if (isState) {
                readRegion(input, level, regionMap, null);
            } else {
                nextSize = input.readShort();
                parent = input.readUTF();
                input.readByte(); // ignore colon -> ':'
                for (j = 0; j < nextSize; j++) {
                    readRegion(input, level, regionMap, parent);
                }
                input.readByte(); // ignore new line -> '\n'
            }
        }
        input.readByte(); // ignore tab -> '\t'
    }
}
